package com.piaoniu.pndao.utils;

import com.piaoniu.pndao.annotations.DaoGen;
import com.piaoniu.pndao.generator.dao.DaoEnv;
import com.piaoniu.pndao.generator.dao.MapperMethod;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.util.Context;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.dom4j.tree.DefaultCDATA;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.lang.model.element.ElementKind;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.piaoniu.pndao.utils.StringHelper.join;
import static com.piaoniu.pndao.utils.StringHelper.lowerFirst;
import static com.piaoniu.pndao.utils.StringHelper.split;
import static com.piaoniu.pndao.utils.StringHelper.toColumnByStyle;

public class DaoGenHelper {

    private Types types;
    private Trees trees;

    public DaoGenHelper(Trees trees, Context context) {
        this.trees = trees;
        this.types = Types.instance(context);
    }

    public static Document parseText(String text) throws DocumentException, SAXException {
        SAXReader reader = new SAXReader(false);
        reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        reader.setFeature("http://apache.org/xml/features/validation/schema", false);
        String encoding = getEncoding(text);

        InputSource source = new InputSource(new StringReader(text));
        source.setEncoding(encoding);

        Document result = reader.read(source);

        // if the XML parser doesn't provide a way to retrieve the encoding,
        // specify it manually
        if (result.getXMLEncoding() == null) {
            result.setXMLEncoding(encoding);
        }

        return result;
    }

    private static String getEncoding(String text) {
        String result = null;

        String xml = text.trim();

        if (xml.startsWith("<?xml")) {
            int end = xml.indexOf("?>");
            String sub = xml.substring(0, end);
            StringTokenizer tokens = new StringTokenizer(sub, " =\"\'");

            while (tokens.hasMoreTokens()) {
                String token = tokens.nextToken();

                if ("encoding".equals(token)) {
                    if (tokens.hasMoreTokens()) {
                        result = tokens.nextToken();
                    }

                    break;
                }
            }
        }

        return result;
    }

    public String mixMethodToData(DaoGen daoGen, String namespace, Map<String, MapperMethod> methodMap, String data) {
        if (data.isEmpty())
            data = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
                    "\n" +
                    "<!DOCTYPE mapper\n" +
                    "PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n" +
                    "\n" +
                    "<mapper namespace=\"" + namespace + "\">\n" +
                    "</mapper>\n";
        try {
            Document document = parseText(data);
            Element element = document.getRootElement();
            element.elements().forEach(sqlEle -> {
                String id = sqlEle.attribute("id").getText();
                methodMap.remove(id);
            });
            methodMap.forEach(getGenFunc(daoGen, element));

            OutputFormat format = OutputFormat.createPrettyPrint();
            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                XMLWriter writer = new XMLWriter(outputStream, format);
                writer.write(document);
                writer.flush();

                return outputStream.toString("UTF-8");
            }
        } catch (DocumentException | IOException | SAXException e) {
            throw new Error(e);
        }
    }

    private BiConsumer<String, MapperMethod> getGenFunc(DaoGen daoGen, Element root) {
        return (key, method) -> {
            if (!(handledWithThisPrefix(key, daoGen::insertPrefix, addInsert(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::updateForPrefix, addUpdateFor(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::updatePrefix, addUpdate(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::findPrefix, addQueryOrFind(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::queryAllPrefix, addQueryAll(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::queryInPrefix, addQueryIn(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::queryPrefix, addQueryOrFind(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::countAllPrefix, addCountAll(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::countPrefix, addCountBy(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::countInPrefix, addCountIn(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::removePrefix, addRemove(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::batchInsertPrefix, addBatchInsert(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::searchPrefix, addSearch(daoGen, key, method, root))
                    || handledWithThisPrefix(key, daoGen::countSearchPrefix, addCountSearch(daoGen, key, method, root))
            )) {
                throw new Error("unknown method to be auto gen:" + key);
            }
        };
    }

    private Consumer<String> addCountSearch(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("select");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);

            sql.addAttribute("resultType", "int");
            sql.addText("SELECT count(1) FROM " + method.getDaoEnv().getTableName());
            sql.addElement("include").addAttribute("refid", "search_where");
        };
    }

    private Consumer<String> addSearch(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("select");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);

            sql.addAttribute("resultType", method.getReturnType().toString());

            List<String> likes = new ArrayList<>();
            List<String> ignores = new ArrayList<>();
            List<String> between = new ArrayList<>();
            List<String> not = new ArrayList<>();

            final String[] configs = daoGen.searchConfig().split(";");
            if (configs.length > 0) {

                for (String config : configs) {
                    final String[] split = config.split(":");
                    final String configKey = split[0];
                    //
                    if ("like".equalsIgnoreCase(configKey)) {
                        final String[] values = split[1].split(",");
                        if (values.length > 0) {
                            likes.addAll(Arrays.asList(values));
                        }
                    }
                    //
                    if ("ignore".equalsIgnoreCase(configKey)) {
                        final String[] values = split[1].split(",");
                        if (values.length > 0) {
                            ignores.addAll(Arrays.asList(values));
                        }
                    }
                    //
                    if ("between".equalsIgnoreCase(configKey)) {
                        final String[] values = split[1].split(",");
                        if (values.length > 0) {
                            between.addAll(Arrays.asList(values));
                        }
                    }
                    if ("not".equalsIgnoreCase(configKey)) {
                        final String[] values = split[1].split(",");
                        if (values.length > 0) {
                            not.addAll(Arrays.asList(values));
                        }
                    }
                }
            }
            List<String> fields = getFields(method.getReturnType());

            sql.addText("\n");
            sql.addText("SELECT " + join(", ", fields.stream().map(f -> String.format("%s as %s", toColumn(f, daoGen), f)).iterator()) + " FROM " + method.getDaoEnv().getTableName());
            sql.addElement("include").addAttribute("refid", "search_where");

            Element where = root
                    .addElement("sql").addAttribute("id", "search_where")
                    .addElement("where");

            for (String field : fields) {
                if (not.contains(field)) {
                    String likeString = "not" + StringHelper.toCapitalize(field);
                    where.addElement("if").addAttribute("test", "criteria." + likeString + " !=null").addText(
                            "AND " + StringHelper.toColumnByStyle(field, daoGen.columnStyle()) + " <>" + " " + ObjField.express(likeString) + ""
                    );
                }
                if (likes.contains(field)) {
                    String likeString = "like" + StringHelper.toCapitalize(field);
                    where.addElement("if").addAttribute("test", "criteria." + likeString + " !=null").addText(
                            "AND " + StringHelper.toColumnByStyle(field, daoGen.columnStyle()) + " like" + " %" + ObjField.express(likeString) + "%"
                    );
                }

                if (between.contains(field)) {

                    String compareString = "start" + StringHelper.toCapitalize(field);
                    where.addElement("if").addAttribute("test", "criteria." + compareString + " !=null").add(
                            new DefaultCDATA("AND " + ObjField.express(compareString) + " < " + StringHelper.toColumnByStyle(field, daoGen.columnStyle()) + " ,")
                    );
                    compareString = "end" + StringHelper.toCapitalize(field);
                    where.addElement("if").addAttribute("test", "criteria." + compareString + " !=null").add(
                            new DefaultCDATA("AND " + ObjField.express(compareString) + " >= " + StringHelper.toColumnByStyle(field, daoGen.columnStyle()) + " ,")

                    );
                }

                if (ignores.contains(field)) {
                    continue;
                }

                where.addElement("if").addAttribute("test", "criteria." + field + " !=null").addText(
                        "AND " + StringHelper.toColumnByStyle(field, daoGen.columnStyle()) + " =" + " " + ObjField.express(field) + ""
                );
            }

            sql.addText("ORDER BY " + daoGen.primaryKey() + " DESC");
        };
    }

    private Consumer<String> addCountIn(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("select");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);
            sql.addAttribute("resultType", "int");

            String left = key.replaceFirst(prefix, "");
            List<String> params = split(left, daoGen.separator());
            StringBuilder select = new StringBuilder(50);
            select.append("SELECT count(1) FROM  ")
                    .append(method.getDaoEnv().getTableName());
            int len = params.size();
            if (len != 1)
                throw new Error("count in method only support one param");
            if (!params.isEmpty()) select.append(" WHERE ");
            String param = params.get(0);

            sql.addText(select.toString());
            if (param.endsWith("s")) param = lowerFirst(param.substring(0, param.length() - 1));

            Element choose = sql.addElement("choose");
            String collection = param + "s";
            Element when = choose.addElement("when");
            when.addAttribute("test", collection + " !=null and " + collection + ".size() > 0");
            when.addText("`" + toColumn(param, daoGen) + "` in ");

            Element each = when.addElement("foreach");
            each.addAttribute("item", param);
            each.addAttribute("collection", param + "s");
            each.addAttribute("open", "(");
            each.addAttribute("separator", ",");
            each.addAttribute("close", ")");
            each.addText("#{" + param + "}");

            Element otherwise = choose.addElement("otherwise");
            otherwise.addText(" 1 = 2 ");

            sql.addText(" ORDER BY " + toColumn(daoGen.primaryKey(), daoGen));
        };
    }

    private Consumer<String> addUpdateFor(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("update");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);

            StringBuilder updateSql = new StringBuilder(50);
            updateSql.append("UPDATE ")
                    .append(method.getDaoEnv().getTableName())
                    .append(" SET \n");

            String left = key.replaceFirst(prefix, "");
            List<String> fields = split(left, daoGen.separator()).stream().map(StringHelper::lowerFirst).collect(Collectors.toList());
            fields.add(method.getDaoEnv().getUpdateTime());
            String pk = daoGen.primaryKey();
            updateSql.append(
                    join(", ",
                            fields.stream().filter((field -> !field.equals(pk) &&
                                    !method.getDaoEnv().getCreateTime().equals(field)))
                                    .map((field -> {
                                        if (method.getDaoEnv().getUpdateTime().equals(field))
                                            return "`" + toColumn(field, daoGen) + "` = " + "now() ";
                                        else return "`" + toColumn(field, daoGen) + "` = " + "#{" + field + "} ";
                                    }))
                                    .iterator()));

            updateSql.append("WHERE `")
                    .append(toColumn(pk, daoGen))
                    .append("` = ")
                    .append("#{")
                    .append(pk)
                    .append("}");
            sql.addText(updateSql.toString());
        };
    }

    private Consumer<String> addQueryIn(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("select");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);
            sql.addAttribute("resultType", method.getReturnType().toString());
            String left = key.replaceFirst(prefix, "");
            List<String> params = split(left, daoGen.separator());
            StringBuilder select = new StringBuilder(50);
            List<String> fields = getFields(method.getReturnType());
            select.append("SELECT ")
                    .append(join(", ", fields.stream().map(f -> String.format("%s as %s", "`" + toColumn(f, daoGen) + "`", f)).iterator()))
                    .append(" FROM ")
                    .append(method.getDaoEnv().getTableName());
            int len = params.size();
            if (len != 1)
                throw new Error("query in method only support one param");
            if (!params.isEmpty()) select.append(" WHERE ");
            String param = params.get(0);

            sql.addText(select.toString());
            if (param.endsWith("s")) param = lowerFirst(param.substring(0, param.length() - 1));

            Element choose = sql.addElement("choose");
            String collection = param + "s";
            Element when = choose.addElement("when");
            when.addAttribute("test", collection + " !=null and " + collection + ".size() > 0");
            when.addText("`" + toColumn(param, daoGen) + "` in ");

            Element each = when.addElement("foreach");
            each.addAttribute("item", param);
            each.addAttribute("collection", param + "s");
            each.addAttribute("open", "(");
            each.addAttribute("separator", ",");
            each.addAttribute("close", ")");
            each.addText("#{" + param + "}");

            Element otherwise = choose.addElement("otherwise");
            otherwise.addText(" 1 = 2 ");

            sql.addText(" order by " + toColumn(daoGen.primaryKey(), daoGen));
        };
    }

    private Consumer<String> addRemove(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("delete");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);
            String left = key.replaceFirst(prefix, "");
            List<String> params = split(left, daoGen.separator());
            if (params.isEmpty()) throw new Error("Remove method needs at least  one param");
            StringBuilder select = new StringBuilder(50);
            select.append("DELETE FROM ")
                    .append(method.getDaoEnv().getTableName());
            if (!params.isEmpty()) select.append(" WHERE ");
            appendParams(params, select, daoGen);
            sql.addText(select.toString());
        };
    }

    private void appendParams(List<String> params, StringBuilder select, DaoGen daoGen) {
        select.append(join(" AND ", params.stream().map(f -> SqlBuilder.eqExpress(lowerFirst(f), daoGen.columnStyle())).iterator()));
    }

    private boolean handledWithThisPrefix(String key, Supplier<String[]> prefixs, Consumer<String> genWithPrefix) {
        for (String prefix : prefixs.get()) {
            if (key.startsWith(prefix)) {
                genWithPrefix.accept(prefix);
                return true;
            }
        }
        return false;
    }

    public <T extends Symbol> List<T> getMember(Class<T> type, ElementKind kind, Symbol classSymbol) {
        List<T> results = new ArrayList<>();
        if (classSymbol.type == null || classSymbol.type.isPrimitiveOrVoid()) {
            return results;
        }
        for (Type t : types.closure(classSymbol.type)) {
            Scope scope = t.tsym.members();
            if (scope == null) continue;
            scope.getElements(symbol -> symbol.getKind() == kind)
                    .forEach(s -> results.add(type.cast(s)));
        }
        if (classSymbol.owner != null && classSymbol != classSymbol.owner
                && classSymbol.owner instanceof Symbol.ClassSymbol) {
            results.addAll(getMember(type, kind, classSymbol.owner));
        }
        if (classSymbol.type.getEnclosingType() != null && classSymbol.hasOuterInstance()) {
            results.addAll(getMember(type, kind, classSymbol.type.getEnclosingType().asElement()));
        }
        return results;
    }

    private Map<String, List<String>> fieldsMap = new HashMap<>();

    private List<String> getFields(Symbol.TypeSymbol type) {
        String typeStr = type.toString();
        if (!fieldsMap.containsKey(typeStr)) {
            List<Symbol.VarSymbol> varSymbols = getMember(Symbol.VarSymbol.class, ElementKind.FIELD, type);
            fieldsMap.put(typeStr, varSymbols.stream().filter(s -> !s.isStatic()).map(Symbol.VarSymbol::toString).collect(Collectors.toList()));
        }
        return fieldsMap.get(typeStr);
    }

    private static final String COMMENT = "added by pn-plugin";

    private Consumer<String> addCountAll(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return addCount(daoGen, key, method, root, (params) -> {
        });
    }

    private Consumer<String> addCountBy(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return addCount(daoGen, key, method, root, (params) -> {
            if (params.isEmpty())
                throw new Error("At least need one param");
        });
    }

    private Consumer<String> addCount(DaoGen daoGen, String key, MapperMethod method, Element root, Consumer<List<String>> validator) {
        return (prefix) -> {
            Element sql = root.addElement("select");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);
            sql.addAttribute("resultType", "int");

            String left = key.replaceFirst(prefix, "");
            List<String> params = split(left, daoGen.separator());
            validator.accept(params);

            final String sql1 = new SELECT(method.getDaoEnv().getTableName())
                    .addSelectColumn(ObjField.snippet("count(1)"))
                    .addWhereConditions(
                            params.stream().map(StringHelper::lowerFirst).map(ObjField::condition).collect(Collectors.toList())
                    )
                    .orderBy(ObjField.order(daoGen.primaryKey()))
                    .sql();
            sql.addText(sql1);
        };
    }

    private Consumer<String> addQueryAll(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return addQuery(daoGen, key, method, root, (params) -> {
        });
    }

    private Consumer<String> addQuery(DaoGen daoGen, String key, MapperMethod method, Element root, Consumer<List<String>> validator) {
        return (prefix) -> {
            Element sql = root.addElement("select");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);
            sql.addAttribute("resultType", method.getReturnType().toString());
            String left = key.replaceFirst(prefix, "");
            String orderClause = "";
            if (left.contains(daoGen.orderBy())) {
                int index = left.indexOf(daoGen.orderBy());
                orderClause = left.substring(index + daoGen.orderBy().length(), left.length());
                left = left.substring(0, index);
            }
            List<String> params = split(left, daoGen.separator());
            validator.accept(params);
            StringBuilder select = new StringBuilder(50);
            List<String> fields = getFields(method.getReturnType());

            select.append("SELECT ")
                    .append(join(", ", fields.stream().map(f -> String.format("%s as %s", toColumn(f, daoGen), f)).iterator()))
                    .append(" FROM ")
                    .append(method.getDaoEnv().getTableName());

            if (!params.isEmpty()) select.append(" WHERE ");
            appendParams(params, select, daoGen);
            String order = "ASC";
            String orderKey = daoGen.primaryKey();
            if (!orderClause.isEmpty()) {
                if (orderClause.contains(daoGen.orderByWith())) {
                    int index = orderClause.indexOf(daoGen.orderByWith());
                    order = orderClause.substring(index + daoGen.orderByWith().length(), orderClause.length());
                    orderKey = orderClause.substring(0, index);
                } else {
                    orderKey = orderClause;
                }
            }
            select.append(" ORDER BY ").append(toColumn(orderKey, daoGen)).append(" ").append(order);
            sql.addText(select.toString());
        };

    }

    private Consumer<String> addQueryOrFind(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return addQuery(daoGen, key, method, root, (params) -> {
            if (params.isEmpty()) throw new Error("At least need one param");
        });
    }

    private Consumer<String> addUpdate(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("update");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);

            StringBuilder updateSql = new StringBuilder(50);
            updateSql.append("\nUPDATE ")
                    .append(method.getDaoEnv().getTableName())
                    .append(" SET \n");

            List<String> fields = getFields(method.getFirstParamType());
            String pk = daoGen.primaryKey();
            String updateByField;
            String entity;
            if (key.startsWith("updateBy")) {
                updateByField = lowerFirst(key.substring(8));
                entity = "entity";
            } else {
                updateByField = pk;
                entity = null;
            }
            updateSql.append(
                    join(", ",
                            fields.stream().filter((field -> !field.equals(pk)
                                    && !field.equals(updateByField)
                                    && !method.getDaoEnv().getCreateTime().equals(field)))
                                    .map((field -> {
                                        if (method.getDaoEnv().getUpdateTime().equals(field))
                                            return toColumn(field, daoGen) + " = " + "now() ";
                                        else
                                            return toColumn(field, daoGen) + " = " + "#{" + (entity != null ? entity + "." : "") + field + "} ";
                                    }))
                                    .iterator()));

            updateSql.append("WHERE ")
                    .append(toColumn(updateByField, daoGen))
                    .append(" = ")
                    .append("#{")
                    .append(updateByField)
                    .append("}");
            sql.addText(updateSql.toString());
        };
    }

    private Stream<String> getInsertFieldsStream(String pk, List<String> fields) {
        return fields.stream().filter((field) -> !pk.equals(field));
    }

    private Consumer<String> addBatchInsert(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("insert");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);

            StringBuilder insertSql = new StringBuilder(50);
            insertSql.append("INSERT INTO ")
                    .append(method.getDaoEnv().getTableName())
                    .append("");

            String pk = daoGen.primaryKey();
            List<String> fields = getFields(method.getFirstParamType());
            insertSql.append("(")
                    .append(join(", ", getInsertFieldsStream(pk, fields).map(f -> toColumn(f, daoGen)).iterator()))
                    .append(")\n");

            insertSql.append("VALUES ");
            sql.addText(insertSql.toString());
            Element foreach = sql.addElement("foreach");
            foreach.addAttribute("collection", "list");
            foreach.addAttribute("item", "item");
            foreach.addAttribute("separator", ",");
            StringBuilder eachSql = new StringBuilder(50);
            eachSql.append("(").append(join(", ", getInsertFieldsStream(pk, fields).map(field -> {
                if (method.getDaoEnv().getCreateTime().contains(field)
                        || method.getDaoEnv().getUpdateTime().contains(field))
                    return "now()";
                else return "#{item." + field + "}";
            }).iterator()));
            eachSql.append(")");
            foreach.addText(eachSql.toString());
        };
    }

    private Consumer<String> addInsert(DaoGen daoGen, String key, MapperMethod method, Element root) {
        return (prefix) -> {
            Element sql = root.addElement("insert");
            sql.addComment(COMMENT);
            sql.addAttribute("id", key);

            StringBuilder insertSql = new StringBuilder(50);
            insertSql.append("INSERT INTO ")
                    .append(method.getDaoEnv().getTableName())
                    .append("\n");

            String pk = daoGen.primaryKey();
            List<String> fields = getFields(method.getFirstParamType());
            insertSql.append("(")
                    .append(join(", ", getInsertFieldsStream(pk, fields).map(f ->  toColumn(f, daoGen)).iterator()))
                    .append(")\n");

            insertSql.append("values (");
            insertSql.append(
                    join(
                            ", ",
                            getInsertFieldsStream(pk, fields).map(field -> {
                                if (method.getDaoEnv().getCreateTime().contains(field)
                                        || method.getDaoEnv().getUpdateTime().contains(field))
                                    return "now()";
                                else return "#{" + field + "}";
                            })
                                    .iterator()
                    )
            );
            insertSql.append(")");
            sql.addText(insertSql.toString());

            Element selectKey = sql.addElement("selectKey");
            selectKey.addAttribute("resultType", "int");
            selectKey.addAttribute("keyProperty", pk);
            selectKey.addText("SELECT LAST_INSERT_ID() AS " + pk);
        };
    }

    public static MapperMethod toMapperMethod(DaoEnv daoEnv, Symbol.MethodSymbol methodSymbol) {
        return new MapperMethod(daoEnv, methodSymbol);
    }

    public static String getMethodName(Symbol.MethodSymbol methodSymbol) {
        return methodSymbol.getSimpleName().toString();
    }

    private static String toColumn(String field, DaoGen daoGen) {
        return toColumnByStyle(field, daoGen.columnStyle());
    }

}
